package com.annet.token.config;

import com.annet.token.annotation.EnableVerifyToken;
import com.annet.token.enums.VerifyTokenEnum;
import com.annet.token.service.ServerSettingServiceImpl;
import com.annet.token.utils.Strings;
import com.annet.token.dto.ResponseDTO;
import com.annet.token.dto.error.ErrorCode;
import com.annet.token.utils.JsonUtil;
import com.annet.token.utils.JwtTokenUtils;
import com.annet.token.utils.MD5Utils;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.net.InetAddress;
import java.util.Date;
import java.util.Map;

/**
 * 接口调用拦截器
 *
 * @author yanhu
 * @date 2018/9/14
 */
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {

    public static boolean useToken = true;

    // token时效
    public static int tokenAging = 1 * 60;

    @Autowired
    private ServerSettingServiceImpl serverSettingService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!useToken) {
            return true;
        }

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            EnableVerifyToken methodAnnotation = handlerMethod.getMethod().getAnnotation(EnableVerifyToken.class);
            if (null != methodAnnotation) {
                VerifyTokenEnum verifyTokenEnum = methodAnnotation.type();
                return !methodAnnotation.value() || verifyAuthToken(request, response, verifyTokenEnum);
            } else {
                EnableVerifyToken annotation = handlerMethod.getBeanType().getAnnotation(EnableVerifyToken.class);
                if (null != annotation) {
                    VerifyTokenEnum verifyTokenEnum = methodAnnotation.type();
                    return !annotation.value() || verifyAuthToken(request, response, verifyTokenEnum);
                }
            }
        }
        return true;
    }

    private boolean verifyAuthToken(HttpServletRequest request, HttpServletResponse response, VerifyTokenEnum verifyTokenEnum) {
        String token = request.getHeader("token");
        String timestamp = request.getHeader("timestamp");
        String sign = request.getHeader("sign");

        switch (verifyTokenEnum) {
            case LOGIN_TOKEN:
                if (Strings.isNotEmpty(token, timestamp, sign)) {
                    // 时效性验证  60秒
                    if ((System.currentTimeMillis() - Long.parseLong(timestamp)) < (tokenAging * 1000L)) {
                        // token验证
                        String userId = JwtTokenUtils.getUserId(token);
                        if (Strings.isNotEmpty(userId)) {
                            // 验证签名
                            String joint = "token=" + token + "&timestamp=" + timestamp;
                            String md5Sign = MD5Utils.getMD5(joint);
                            if (sign.toUpperCase().equals(md5Sign.toUpperCase())) {
                                // 校验通过
                                request.setAttribute("userId", userId);
                                // 并发控制
                                return concurrencyControl(userId, request.getServletPath());
                            }
                        }
                    }
                }
                break;
            case AUTHORIZE_TOKEN:
                if (Strings.isNotEmpty(token, timestamp, sign)) {
                    // 时效性验证  60秒
                    if ((System.currentTimeMillis() - Long.parseLong(timestamp)) < (tokenAging * 1000L)) {
                        Map<String, Claim> claimMap = JwtTokenUtils.verifyToken(token, serverSettingService.getSecret());
                        if (null == claimMap) return false;
                        String orgCode = claimMap.get("orgCode").asString();
                        String uname = claimMap.get("uname").asString();
                        String pwd = claimMap.get("pwd").asString();
                        String enableIps = claimMap.get("enableIps").asString();
                        String unableIps = claimMap.get("unableIps").asString();
                        Date issuedAt = claimMap.get("iat").asDate();
                        if (Strings.isNotEmpty(orgCode, uname, pwd) && serverSettingService.existsSetting(orgCode, uname, pwd)) {
                            //有效期
//                        if ((System.currentTimeMillis() - issuedAt.getTime()) < (tokenAging * 1000L)) {}
                            String ip = parseIp(request);
                            if (enableIps.contains(ip) || !unableIps.contains(ip)) { //ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")
                                // 验证签名
                                String joint = "token=" + token + "&timestamp=" + timestamp;
                                String md5Sign = MD5Utils.getMD5(joint);
                                if (sign.toUpperCase().equals(md5Sign.toUpperCase())) {
                                    return true;
                                }
                            }

                        }
                    }
                }
                break;
            default:
                log.error("token校验不通过 VerifyTokenEnum有误");
                break;

        }
        log.debug("token校验不通过 path:{}", request.getRequestURL());
        printError(response);
        return false;
    }

    private static boolean verifyToken(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader("token");
        String timestamp = request.getHeader("timestamp");
        String sign = request.getHeader("sign");

        if (Strings.isNotEmpty(token, timestamp, sign)) {
            // 时效性验证  60秒
            if ((System.currentTimeMillis() - Long.parseLong(timestamp)) < (tokenAging * 1000L)) {
                // token验证
                String userId = JwtTokenUtils.getUserId(token);
                if (Strings.isNotEmpty(userId)) {
                    // 验证签名
                    String joint = "token=" + token + "&timestamp=" + timestamp;
                    String md5Sign = MD5Utils.getMD5(joint);
                    if (sign.toUpperCase().equals(md5Sign.toUpperCase())) {
                        // 校验通过
                        request.setAttribute("userId", userId);
                        // 并发控制
                        return concurrencyControl(userId, request.getServletPath());
                    }
                }
            }
        }
        log.debug("token校验不通过 path:{}", request.getRequestURL());
        printError(response);
        return false;
    }

    private static void printError(ServletResponse response) {
        response.setContentType("application/json; charset=utf-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.print(JsonUtil.bean2Json(ResponseDTO.onError(ErrorCode.TOKEN_SIGN_ERROR_OR_EXPIRY)));
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 并发缓存
//    private static ConcurrentHashMap<String, Boolean> concurrentSignalCache = new ConcurrentHashMap<>();

//    private static final ThreadLocal<String> keyLocal = new ThreadLocal<>();

    /**
     * 并发控制，可以使用redis替代
     *
     * @param userId
     */
    private static boolean concurrencyControl(String userId, String servletPath) {
//        String key = userId + servletPath;
//        while (true) {
//            if (null == concurrentSignalCache.get(key)) {
//                concurrentSignalCache.put(key, true);
//                keyLocal.set(key);
//                break;
//            }
//            Threads.sleep(10);
//        }
        return true;
    }

    private static void printConcurrentMsg(ServletResponse response) throws IOException {
        response.setContentType("application/json; charset=utf-8");
        ServletOutputStream pw = response.getOutputStream();
        PrintWriter p = new PrintWriter(pw);
        p.print(JsonUtil.bean2Json(ResponseDTO.onError(ErrorCode.CONCURRENT_REQUEST_MSG)));
        p.close();
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView mv) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
//        try {
//            String key = keyLocal.get();
//            if (null != key) {
//                concurrentSignalCache.remove(key);
//            }
//        } finally {
//            keyLocal.remove();
//        }
    }

    /**
     * 解析IP
     *
     * @param request HttpServletRequest
     * @return 请求IP地址
     */
    public static String parseIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1")) {
                // 根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (inet != null) {
                    ip = inet.getHostAddress();
                }
            }
        }
        // 多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }

}
